package com.qingcloud.common.auth.config;

import cn.hutool.core.util.StrUtil;
import com.qingcloud.base.constant.SecurityConstants;
import com.qingcloud.base.remote.pojo.LoginUserDTO;
import com.qingcloud.common.auth.constants.IdTokenClaimNames;
import com.qingcloud.common.auth.granter.*;
import com.qingcloud.common.auth.model.Client;
import com.qingcloud.common.auth.properties.SecurityProperties;
import com.qingcloud.common.auth.properties.TokenStoreProperties;
import com.qingcloud.common.auth.service.IValidateCodeService;
import com.qingcloud.common.auth.service.OauthClientDetailService;
import com.qingcloud.common.auth.service.impl.CustomTokenServices;
import com.qingcloud.common.auth.service.impl.UserDetailServiceFactory;
import com.qingcloud.common.auth.service.impl.UserDetailsByNameServiceFactoryWrapper;
import com.qingcloud.common.auth.util.AuthUtils;
import com.qingcloud.common.auth.utils.OidcIdTokenBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;

import javax.annotation.Resource;
import java.util.*;

/**
 * token授权模式配置类
 */
@Configuration
public class TokenGranterConfig {
    protected final Log logger = LogFactory.getLog(getClass());

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Resource
    private UserDetailServiceFactory userDetailsServiceFactory;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private TokenStore tokenStore;
    //
    @Autowired
    private List<TokenEnhancer> tokenEnhancer;

    @Autowired
    private IValidateCodeService validateCodeService;

    @Autowired
    private RandomValueAuthorizationCodeServices authorizationCodeServices;

    private boolean reuseRefreshToken = true;
    //
    private TokenGranter tokenGranter;

    @Resource
    private SecurityProperties securityProperties;

    /**
     * 授权模式
     */
    @Bean
    @ConditionalOnMissingBean
    public TokenGranter tokenGranter(DefaultTokenServices tokenServices) {
        if (tokenGranter == null) {
            tokenGranter = new TokenGranter() {
                private CompositeTokenGranter delegate;

                @Override
                public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
                    if (delegate == null) {
                        delegate = new CompositeTokenGranter(getAllTokenGranters(tokenServices));
                    }
                    return delegate.grant(grantType, tokenRequest);
                }
            };
        }
        return tokenGranter;
    }

    /**
     * 所有授权模式：默认的5种模式 + 自定义的模式
     */
    protected List<TokenGranter> getAllTokenGranters(DefaultTokenServices tokenServices) {
        AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
        OAuth2RequestFactory requestFactory = requestFactory();
        //获取默认的授权模式
        List<TokenGranter> tokenGranters = getDefaultTokenGranters(tokenServices, authorizationCodeServices, requestFactory);
        if (authenticationManager != null) {
            // 添加密码加图形验证码模式
            tokenGranters.add(new PwdImgCodeGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory, validateCodeService, securityProperties.getAuth().getUnValid()));
            // 添加openId模式
            tokenGranters.add(new OpenIdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
            // 添加手机号加密码授权模式
            tokenGranters.add(new MobilePwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
            //邮箱验证码授权模式
            tokenGranters.add(new EmailCodeGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
            //自动登录授权
            tokenGranters.add(new AutoLoginGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
        }
        return tokenGranters;
    }

    /**
     * 默认的授权模式
     */
    private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerTokenServices tokenServices
            , AuthorizationCodeServices authorizationCodeServices, OAuth2RequestFactory requestFactory) {
        List<TokenGranter> tokenGranters = new ArrayList<>();
        // 添加授权码模式
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory));
        // 添加刷新令牌的模式
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加隐士授权模式
        tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加客户端模式
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
        if (authenticationManager != null) {
            // 添加密码模式
            tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
        }
        return tokenGranters;
    }

    private AuthorizationCodeServices authorizationCodeServices() {
        if (authorizationCodeServices == null) {
            authorizationCodeServices = new InMemoryAuthorizationCodeServices();
        }
        return authorizationCodeServices;
    }

    private OAuth2RequestFactory requestFactory() {
        return new DefaultOAuth2RequestFactory(clientDetailsService);
    }

    @Bean
    @Order(2)
    @ConditionalOnMissingBean
    protected DefaultTokenServices createDefaultTokenServices() {
        DefaultTokenServices tokenServices = new CustomTokenServices(securityProperties.getAuth());
        tokenServices.setTokenStore(tokenStore);
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setReuseRefreshToken(reuseRefreshToken);
        tokenServices.setClientDetailsService(clientDetailsService);
        tokenServices.setTokenEnhancer(tokenEnhancer());
        addUserDetailsService(tokenServices);
        return tokenServices;
    }

    @Bean
    @Order(1)
    @ConditionalOnMissingBean
    public TokenEnhancer tokenEnhancer(@Autowired(required = false) KeyProperties keyProperties
            , TokenStoreProperties tokenStoreProperties
            , OauthClientDetailService oauthClientDetailService) {
        return (accessToken, authentication) -> {
            Set<String> responseTypes = authentication.getOAuth2Request().getResponseTypes();
            Map<String, Object> additionalInfo = new HashMap<>(3);

            int isadmin = 0;
            Object principal = authentication.getPrincipal();
            if (principal instanceof LoginUserDTO) {
                LoginUserDTO loginUserDTO = ((LoginUserDTO) principal);
                additionalInfo.put(SecurityConstants.LOGIN_USER_PARAM_USERNAME, loginUserDTO.getUsername());
                additionalInfo.put(SecurityConstants.LOGIN_USER_PARAM_USERID, loginUserDTO.getUserId());
                additionalInfo.put(SecurityConstants.LOGIN_USER_PARAM_FULLNAME, loginUserDTO.getFullName());
                additionalInfo.put(SecurityConstants.LOGIN_USER_PARAM_ORG_ID, loginUserDTO.getOrgId());
                if (loginUserDTO.getUsername().equals(SecurityConstants.ADMIN_USER_NAME)) {
                    isadmin = 1;
                }
                additionalInfo.put(SecurityConstants.LOGIN_USER_PARAM_ISADMIN, isadmin);
            }
            String accountType = AuthUtils.getAccountType(authentication.getUserAuthentication());
            if (StrUtil.isNotEmpty(accountType)) {
                additionalInfo.put(SecurityConstants.ACCOUNT_TYPE_PARAM_NAME, accountType);
            }
            OAuth2Request request = authentication.getOAuth2Request();
            if (responseTypes.contains(SecurityConstants.ID_TOKEN)
                    || "authJwt".equals(tokenStoreProperties.getType())) {
                //增加id参数
                if (principal instanceof LoginUserDTO) {
                    LoginUserDTO user = (LoginUserDTO) principal;
                    if (responseTypes.contains(SecurityConstants.ID_TOKEN)) {
                        //生成id_token
                        setIdToken(additionalInfo, authentication, keyProperties, oauthClientDetailService, user);
                    }
                    if ("authJwt".equals(tokenStoreProperties.getType())) {
                        additionalInfo.put("id", user.getUserId());
                    }
                }
            }
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            return accessToken;
        };
    }

    /**
     * 生成id_token
     *
     * @param additionalInfo           存储token附加信息对象
     * @param authentication           授权对象
     * @param keyProperties            密钥
     * @param oauthClientDetailService oauth应用service
     */
    private void setIdToken(Map<String, Object> additionalInfo, OAuth2Authentication authentication
            , KeyProperties keyProperties, OauthClientDetailService oauthClientDetailService, LoginUserDTO user) {
        String clientId = authentication.getOAuth2Request().getClientId();
        Client client = oauthClientDetailService.loadClientByClientId(clientId);
        if (client.getSupportIdToken()) {
            String nonce = authentication.getOAuth2Request().getRequestParameters().get(IdTokenClaimNames.NONCE);
            long now = System.currentTimeMillis();
            long expiresAt = System.currentTimeMillis() + client.getIdTokenValiditySeconds() * 1000;
            String idToken = OidcIdTokenBuilder.builder(keyProperties)
                    .issuer(SecurityConstants.ISS)
                    .issuedAt(now)
                    .expiresAt(expiresAt)
                    .subject(user.getUserId())
                    .loginName(user.getUsername())
                    .audience(clientId)
                    .nonce(nonce)
                    .build();

            additionalInfo.put(SecurityConstants.ID_TOKEN, idToken);
        }
    }

    private TokenEnhancer tokenEnhancer() {
        if (tokenEnhancer != null) {
            TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
            tokenEnhancerChain.setTokenEnhancers(tokenEnhancer);
            return tokenEnhancerChain;
        }
        return null;
    }

    private void addUserDetailsService(DefaultTokenServices tokenServices) {
        if (this.userDetailsServiceFactory != null) {
            PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
            provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceFactoryWrapper<>(this.userDetailsServiceFactory));
            tokenServices.setAuthenticationManager(new ProviderManager(Collections.singletonList(provider)));
        }
    }
}
